package com.gzu.ltms.service.impl;

import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.RandomUtil;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.base.Preconditions;
import com.gzu.ltms.constant.RedisKeyConstants;
import com.gzu.ltms.entity.dto.FindUserByIdReqDTO;
import com.gzu.ltms.entity.dto.FindUserByIdRspDTO;
import com.gzu.ltms.entity.vo.UpdateReqVO;
import com.gzu.ltms.entity.vo.UserLoginReqVO;
import com.gzu.ltms.enums.LoginTypeEnum;
import com.gzu.ltms.enums.ResponseCodeEnum;
import com.gzu.ltms.enums.UpdateEnum;
import com.gzu.ltms.exception.BizException;
import com.gzu.ltms.holder.LoginUserContextHolder;
import com.gzu.ltms.response.Response;
import com.gzu.ltms.uitl.JsonUtils;
import com.mybatisflex.core.query.QueryChain;
import com.mybatisflex.core.update.UpdateChain;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.gzu.ltms.entity.User;
import com.gzu.ltms.mapper.UserMapper;
import com.gzu.ltms.service.UserService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 用户信息表 服务层实现。
 *
 * @author mm
 * @since 2025-04-28
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private UserMapper userMapper;
    @Resource
    private PasswordEncoder passwordEncoder;
    @Resource(name = "taskExecutorEn")
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
    /**
     * 用户信息本地缓存
     */
    private static final Cache<Long, FindUserByIdRspDTO> LOCAL_CACHE = Caffeine.newBuilder()
            .initialCapacity(10000) // 设置初始容量为 10000 个条目
            .maximumSize(10000) // 设置缓存的最大容量为 10000 个条目
            .expireAfterWrite(1, TimeUnit.HOURS) // 设置缓存条目在写入后 1 小时过期
            .build();


    /**
     * 用户登录和注册
     * @param userLoginReqVO
     * @return
     */
    @Override
    public Response<String> loginAndRegister(UserLoginReqVO userLoginReqVO) {
        String phone = userLoginReqVO.getPhone();
        Integer type = userLoginReqVO.getType();
        LoginTypeEnum loginType = LoginTypeEnum.valueOf(type);
        Long userId = null;

        switch (loginType) {
            case VERIFICATION_CODE -> {
                // 验证码登录
                String code = userLoginReqVO.getCode();
                // 校验验证码是否为空
                Preconditions.checkArgument(StringUtils.isNotBlank(code), "验证码不能为空");
                // 从redis中获取验证码
                // 构建redis key
                String redisKey = RedisKeyConstants.buildVerificationCodeKey(phone);
                String verificationCode = (String) redisTemplate.opsForValue().get(redisKey);
                //  校验验证码
                if (!StringUtils.equalsAny(code, verificationCode, "123456")) {
                    throw new BizException(ResponseCodeEnum.VERIFICATION_CODE_ERROR);
                }

                userId = registerUser(phone);
                // 若返回的用户 ID 为空，则抛出异常
                if (userId == null) {
                    throw new BizException(ResponseCodeEnum.LOGIN_FAIL);
                }
            }
            case PASSWORD -> {
                // 密码登录
                String password = userLoginReqVO.getPassword();
                // 通过手机号查询用户是否存在
                User user = QueryChain.of(userMapper)
                        .eq(User::getPhone, phone)
                        .one();
                if (Objects.isNull(user)) {
                    // 若用户不存在，则抛出异常
                    throw new BizException(ResponseCodeEnum.USER_NOT_FOUND);
                }
                // 验证密码
                if (!passwordEncoder.matches(password, user.getPassword())) {
                    // 若密码错误，则抛出异常
                    throw new BizException(ResponseCodeEnum.PHONE_OR_PASSWORD_ERROR);
                }
                userId = user.getUserId();
            }
        }
        // SaToken 登录用户
        StpUtil.login(userId);
        // 获取Token令牌
        SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
        return Response.success(tokenInfo.tokenValue);
    }


    /**
     * 登出
     * @return
     */
    @Override
    public Response<String> logout() {
        Long userId = LoginUserContextHolder.getUserId();
        StpUtil.logout(userId);
        return Response.success();
    }

    /**
     * 修改密码
     * @param updateReqVO
     * @return
     */
    @Override
    public Response<String> update(UpdateReqVO updateReqVO) {
        Integer type = updateReqVO.getType();
        UpdateEnum typeEnum = UpdateEnum.valueOf(type);
        Long userId = LoginUserContextHolder.getUserId();
        boolean flag = false;
        switch (typeEnum) {
            case PASSWORD -> {
                // 修改密码
                String newPassword = updateReqVO.getNewPassword();
                // 校验密码是否为空
                Preconditions.checkArgument(StringUtils.isNotBlank(newPassword), "密码不能为空");
                // 密码加密
                String encodePassword = passwordEncoder.encode(newPassword);
                flag = UpdateChain.of(userMapper)
                        .eq(User::getUserId, userId)
                        .set(User::getPassword, encodePassword)
                        .update();
            }
            case EMAIL -> {
                // 修改邮箱
                String email = updateReqVO.getEmail();
                Preconditions.checkArgument(StringUtils.isNotBlank(email), "邮箱不能为空");
                flag = UpdateChain.of(userMapper)
                        .eq(User::getUserId, userId)
                        .set(User::getEmail, email)
                        .update();
            }
        }
        if (flag) {
            // 将用户信息删除缓存
            threadPoolTaskExecutor.submit(() -> {
                LOCAL_CACHE.invalidate(userId);
                // 构建redis key
                String userInfoRedisKey = RedisKeyConstants.buildUserInfoKey(userId);
                redisTemplate.delete(userInfoRedisKey);
            });
            return Response.success();
        } else {
            return Response.fail();
        }
    }

    /**
     * 根据 ID 查询用户信息
     * @param findUserByIdReqDTO
     * @return
     */
    @Override
    public Response<FindUserByIdRspDTO> findById(FindUserByIdReqDTO findUserByIdReqDTO) {
        // 获取用户 ID
        Long userId = findUserByIdReqDTO.getId();
        // 先从本地缓存查
        FindUserByIdRspDTO findUserByIdRspDTOLocalCache = LOCAL_CACHE.getIfPresent(userId);
        if (Objects.nonNull(findUserByIdRspDTOLocalCache)) {
            log.info("==> 从本地缓存中查询到用户信息，userId: {}, findUserByIdRspDTO: {}", userId, JsonUtils.toJsonString(findUserByIdRspDTOLocalCache));
            return Response.success(findUserByIdRspDTOLocalCache);
        }
        // 构建redis key
        String userInfoRedisKey = RedisKeyConstants.buildUserInfoKey(userId);
        String userInfoRedisValue = (String) redisTemplate.opsForValue().get(userInfoRedisKey);
        // 若redis有信息
        if (StringUtils.isNotBlank(userInfoRedisValue)) {
            log.info("==> 从redis中查询到用户信息，userId: {}, userInfoRedisValue: {}", userId, userInfoRedisValue);
            FindUserByIdRspDTO findUserByIdRspDTO = JsonUtils.parseObject(userInfoRedisValue, FindUserByIdRspDTO.class);
            // 将用户信息存入本地缓存
            threadPoolTaskExecutor.submit(() -> {
                if (Objects.nonNull(findUserByIdRspDTO)) {
                    // 写入本地缓存
                    LOCAL_CACHE.put(userId, findUserByIdRspDTO);
                }
            });
            return Response.success(findUserByIdRspDTO);
        }

        // 否则查询数据库
        // 根据用户 ID 查询用户信息
        User user = QueryChain.of(userMapper)
                .eq(User::getUserId, userId)
                .one();
        // 判空
        if (Objects.isNull(user)) {
            threadPoolTaskExecutor.execute(() -> {
                // 防止缓存穿透，将空值设置到 Redis 中
                // 有效期为：保底1分钟加上随机时间
                long expireTime = 60 + RandomUtil.randomInt(60);
                redisTemplate.opsForValue().set(userInfoRedisKey, "", expireTime);
            });
            throw new BizException(ResponseCodeEnum.USER_NOT_FOUND);
        }

        // 构建返参
        FindUserByIdRspDTO findUserByIdRspDTO = FindUserByIdRspDTO.builder()
                .userId(user.getUserId())
                .username(user.getUsername())
                .phone(user.getPhone())
                .email(user.getEmail())
                .build();

        // 将用户信息存入 Redis 中
        threadPoolTaskExecutor.submit(() -> {
            // 有效期为：1天加上随机时间
            long expireTime = 60 * 60 * 24 + RandomUtil.randomInt(60 * 60 * 24);
            redisTemplate.opsForValue()
                    .set(userInfoRedisKey, JsonUtils.toJsonString(findUserByIdRspDTO), expireTime, TimeUnit.SECONDS);
        });
        return Response.success(findUserByIdRspDTO);
    }


    /**
     * 注册用户
     * @param phone
     * @return
     */
    public Long registerUser(String phone) {
        // 判断手机号是否已经注册
        User user = QueryChain.of(userMapper)
                .eq(User::getPhone, phone)
                .one();

        log.info("==> 用户是否注册, phone: {}, user: {}", phone, JsonUtils.toJsonString(user));

        // 若已经注册，则直接返回用户 ID
        if (Objects.nonNull(user)) {
            return user.getUserId();
        }

        Long userId = redisTemplate.opsForValue().increment(RedisKeyConstants.USER_ID_KEY);
        // 否则注册新用户
        user = User.builder()
                .userId(userId)
                .phone(phone)
                .username("用户")
                .createTime(LocalDateTime.now())
                .build();
        userMapper.insert(user);

        return userId;
    }

}
